/**
 * @description 任务
*/

const fs = require('fs')
const { resolve } = require('path')
const Bagpipe = require('bagpipe')
const request = require('request')
const { StoreFile, dater } = require('@daelui/oxkit/dist/index.js')
const user_agent_list = require('../../constant/user-agent.js') // 浏览器
const config = global.config
const tile = config.tile || {}
tile.path = tile.path || './xdata'

class Task {
  constructor (options) {
    options = options || {}
    let params = options.params || {}
    this.layer = options.layer
    this.range = params.range // 范围
    this.levels = params.levels // 层级
    let rule = tile.rule
    // 存储目录按规则进行替换
    this.path = rule
      .replace('{tilePath}', tile.path)
      .replace('{layerMap}', this.layer.map)
      .replace('{layerPath}', this.layer.path)
    this.path = this.replaceDate(this.path)
    // 递增存储
    let increaseRule = tile.increaseRule
    if (increaseRule) {
      this.increasePath = increaseRule
        .replace('{increasePath}', tile.increasePath)
        .replace('{layerMap}', this.layer.map)
        .replace('{layerPath}', this.layer.path)
      this.increasePath = this.replaceDate(this.increasePath)
    }
    // 初始化瓦片集合
    this.allTiles = []
    // 创建下载队列
    this.sum = 0
    this.bag = new Bagpipe(tile.speed || 10, { timeout: 1000 })
  }

  // 日期替换
  replaceDate (path) {
    let date = new Date()
    let year = date.getFullYear()
    let month = date.getMonth() + 1
    let dates = date.getDate()
    let hours = date.getHours()
    let minutes = date.getMinutes()
    let seconds = date.getSeconds()

    path = path.replace('{yyyy}', year)
      .replace('{yy}', ('0' + year).slice(-2))
      .replace('{MM}', ('0' + month).slice(-2))
      .replace('{M}', month)
      .replace('{dd}', ('0' + dates).slice(-2))
      .replace('{d}', dates)
      .replace('{HH}', ('0' + hours).slice(-2))
      .replace('{H}', hours)
      .replace('{hh}', ('0' + hours % 12).slice(-2))
      .replace('{h}', hours % 12)
      .replace('{mm}', ('0' + minutes).slice(-2))
      .replace('{m}', minutes)
      .replace('{ss}', ('0' + seconds).slice(-2))
      .replace('{s}', seconds)

    return path
  }

  // 执行任务
  run () {
    // 计算瓦片坐标
    this.calcTiles(this.range, this.levels)
    // 创建目录下载
    this.createDir(this.path)
    if (this.increasePath) {
      this.createDir(this.increasePath, false)
    }
  }

  /**
  * @function 计算每个层级的瓦片坐标
  * @param {Arr} bounding 范围
  * @param {Arr} levels 层级
  */
  calcTiles(bounding, levels) {
    levels.forEach((i) => {
      let node = {}
      let p1 = this.calcXY(bounding[2], bounding[3], i)
      let p2 = this.calcXY(bounding[0], bounding[1], i)
      node.t = i // 层级
      node.x = this.sort([p2.tile_X, p1.tile_X]) // 瓦片横坐标范围（左至右）
      node.y = this.sort([p1.tile_Y, p2.tile_Y]) // 瓦片纵坐标范围（下至上）
      this.allTiles.push(node)
    })
  }

  /**
   * @function 计算经纬度转换成瓦片坐标
   * @param {Number} lng 经度 
   * @param {Number} lat 纬度
   * @param {Number} level 层级 
   */
  calcXY(lng, lat, level) {
    let x = (lng + 180) / 360
    let tile_X = Math.floor(x * Math.pow(2, level))
    let lat_rad = lat * Math.PI / 180
    let y = (1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math.PI) / 2
    let tile_Y = Math.floor(y * Math.pow(2, level))
    return { tile_X, tile_Y }
  }

  // 按小到大排序
  sort (arr) {
    return arr.sort(function(a, b){
      return a > b ? 1 : a < b ? -1 : 0
    })
  }

  // 创建目录
  createDir(tilePath, isTask) {
    this.buildPath(tilePath)
    fs.access(tilePath, fs.constants.F_OK, err => {
      // 创建tiles文件夹
      if (err) fs.mkdir(tilePath, err => { })
      for (let z = 0; z <= this.allTiles.length - 1; z++) {
        fs.access(`${tilePath}/${this.allTiles[z].t}`, fs.constants.F_OK, err => {
          // 创建tiles/Z文件夹 ,Z是层级
          if (err) fs.mkdir(`${tilePath}/${this.allTiles[z].t}`, err => { })
          for (let x = this.allTiles[z].x[0]; x <= this.allTiles[z].x[1]; x++) {
            fs.access(`${tilePath}/${this.allTiles[z].t}/${x}`, fs.constants.F_OK, err => {
              // 创建tiles/Z/X文件夹 ,X是瓦片横坐标
              if (err) fs.mkdir(`${tilePath}/${this.allTiles[z].t}/${x}`, err => { })
            })
          }
        })
      }
      if (isTask === false) {
        return true
      }
      // 文件夹可能较多，等待2s开始下载
      setTimeout(() => {
        this.buildTask()
      }, 2000)
    })
  }

  /**
   * @function 创建路径
   * @param {String} folderpath
  */
  buildPath (folderpath) {
    if (typeof folderpath !== 'string') {
      return false
    }
    folderpath = resolve(folderpath).replace(/\\/g, '/')
    const pathArr = folderpath.split('/')
    let _path = ''
    for (let i = 0; i < pathArr.length; i++) {
      // 盘符
      if (!i && /\w+:/.test(pathArr[i])) {
        _path += pathArr[i]
      }
      else if (pathArr[i]) {
        _path += `/${pathArr[i]}`
        if (!fs.existsSync(_path)) {
          fs.mkdirSync(_path)
        }
      }
    }
  }

  // 创建任务
  buildTask () {
    let tiles = []
    for (let z = 0; z <= this.allTiles.length - 1; z++) {
      let item = this.allTiles[z]
      for (let x = item.x[0]; x <= item.x[1]; x++) {
        for (let y = item.y[0]; y <= item.y[1]; y++) {
          // 将下载任务推入队列
          ++this.sum
          let tile = { x, y, z: item.t }
          tiles.push(tile)
          this.bag.push(this.download.bind(this), tile)
        }
      }
    }

    // 存储任务
    let store = new StoreFile({
      path: tile.taskPath,
      key: 'tile_db'
    })
    let data = {
      map: this.layer.map,
      path: this.layer.path,
      range: this.range, // 范围
      levels: this.levels, // 层级
      time: dater.format(Date.now()),
      sum: this.sum,
      status: 1,
      tiles: tiles
    }
    store.setItem(this.layer.map + '/' + this.layer.path, data)
    this.data = data
    this.store = store
  }

  /**
  * @function 下载图片方法
  * @param {Object} item 瓦片对象
  */
  download (item) {
    let x = item.x, y = item.y, z = item.z
    let url = this.getUrl(item)
    if (this.isStoped) {
      return true
    }
    if (!url) {
      --this.sum
      this.done(item)
      return true
    }
    // 协议处理
    if (/^\/\//.test(url)) {
      url = 'http:' + url
    }
    var ip = this.getIp() // 随机生成IP迷惑服务器
    var v = Math.floor(Math.random() * user_agent_list.length) // 随机生成浏览器信息
    var options = {
      method: 'GET',
      url: url,
      headers: {
        'User-Agent': user_agent_list[v],
        'X-Forwarded-For': ip,
        'Connection': 'keep-alive'
      },
      timeout: 5000,
      forever: true
    }

    // 下载并保存图片
    request(options, (err, res, body) => {
      --this.sum
      if (err) {
        // this.bag.push(download, x, y, z) // 重新下载
        // console.log(`第${z}/${x}/${y}层`)
        // console.log('request错误', err)
      }
    }).pipe(fs.createWriteStream(`${this.path}/${z}/${x}/${y}.png`).on('finish', () => {
      --this.sum
      this.done(item)
      // 递增存储
      if (this.increasePath) {
        fs.copyFile(`${this.path}/${z}/${x}/${y}.png`, `${this.increasePath}/${z}/${x}/${y}.png`, function(){})
      }
      // console.log(`图片下载成功,第${z}/${x}/${y}层`)
      // console.log(--this.sum)
    }).on('error', (err) => {
      if (err) {
        this.done(item, false)
      }
      // console.log(`第${z}/${x}/${y}层`)
      // console.log('发生异常:', err)
    }))
  }

  // 停止任务
  stop () {
    this.isStoped = true
    this.bag.queue = []
    this.bag.limit = 0
    this.done()
    // 更新任务状态
    if (this.data) {
      this.data.status = 0 // 0: 已停止, 1: 运行中, 2: 已完成
      this.store.setItem(this.layer.map + '/' + this.layer.path, this.data)
    }
  }

  // 恢复任务
  resume () {
    this.isStoped = false
    this.bag.limit = tile.speed || 10
    
    // 重新启动未完成的下载
    if (this.data && this.data.tiles) {
      let unfinishedTiles = this.data.tiles.filter(tile => !tile.success && !tile.fail)
      unfinishedTiles.forEach(tile => {
        this.bag.push(this.download.bind(this), tile)
      })
    }
  }

  // 获取下载地图
  getUrl (item) {
    return ''
  }

  // 完成
  done (item, flag) {
    if (this.data && this.data.tiles) {
      this.data.tiles.forEach(node => {
        if (flag === false) {
          node.fail = 1
        } else if (node === item) {
          node.success = 1
        }
      })
      
      // 检查任务是否完成
      let successCount = this.data.tiles.filter(tile => tile.success === 1).length
      let failCount = this.data.tiles.filter(tile => tile.fail === 1).length
      let totalCount = this.data.tiles.length
      
      if (successCount + failCount >= totalCount) {
        this.data.status = 2 // 任务完成
        this.isStoped = true
      } else if (!this.isStoped) {
        this.data.status = 1 // 运行中
      }
      
      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.store.setItem(this.layer.map + '/' + this.layer.path, this.data)
      }, 5000)
      
      // 每3秒保存一次
      if (this.startTime === undefined) {
        this.startTime = Date.now()
      }
      let currTime = Date.now()
      if (currTime - this.startTime >= 3000) {
        this.startTime = currTime
        this.store.setItem(this.layer.map + '/' + this.layer.path, this.data)
      }
    }
  }

  // 获取IP
  getIp () {
    return Math.floor(Math.random() * 256) // 随机生成IP迷惑服务器
    + '.' + Math.floor(Math.random() * 256)
    + '.' + Math.floor(Math.random() * 256)
    + '.' + Math.floor(Math.random() * 256)
  }

  // 合并地址与参数
  mergeQuery (url, data) {
    if (typeof url === 'string' && Object.prototype.toString.call(data) === '[object Object]') {
      let query = this.stringify(data)
      let symbol = /\?/.test(url) ? '&' : '?'
      url += query ? symbol + query : ''
    }
    return url
  }

  /**
   * @function 解析参数对象
   * @param {Object} obj 参数对象
   * @return {Object}
  */
	stringify (obj, sep, eq) {
    sep = sep || '&'
    eq = eq || '='
    let list = []
    for (var k in obj) {
      list.push(encodeURIComponent(k) + eq + encodeURIComponent(obj[k]))
    }
    return list.join(sep)
  }
}

module.exports = Task